/*
 * @Author: yayalee yangcheng960813@163.com
 * @Date: 2024-05-16 22:40:14
 * @LastEditTime: 2024-07-18 11:06:49
 * @LastEditors: yayalee yangcheng960813@163.com
 * @Description: 
 * @FilePath: \unfinished-challenges\assets\core_ts\TsUtil.ts
 * 可以输入预定的版权声明、个性签名、空行等
 */

/**
 * @description: 获取变量类型
 * @param {any} value
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function getVarType(value: any): string {
    return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}

/**
 * @description: 判断是否是 undefined null or NaN
 * @param {any} value
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function isNull(value: any): boolean {
    return value === null || value === undefined;
}

/**
 * @description: 判断value是否为空(null、undefined、空对象、空数组、...)
 * @param {any} value
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 * ```
 * isEmpty({})                      // true
 * isEmpty({id: '1'})               // false
 * isEmpty([])                      // true
 * isEmpty([1, 2, 3])               // false
 * isEmpty(new Set())               // true
 * isEmpty(new Set([]))             // true
 * isEmpty(new Set([10, 11, 12]))   // false
 * ```
 */
function isEmpty(value: any): boolean {
    if (isNull(value)) return true;
    if (isString(value) || isArray(value)) return !value.length;
    if (isMap(value) || isSet(value)) return !value.size;
    if (isObject(value)) return !Object.keys(value).length;
    if (isBoolean(value)) return false;
    // console.warn(`isEmpty() 方法仅支持检查value是否为null/undefined，以及Array、String、Map、Set和Object是否为空。`);
    return false;
}

/**
 * @description: 是否为 number
 * @param {any} value
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function isNumber(value: any): boolean {
    const type = typeof value;
    return type === 'number' || (type === 'object' && getVarType(value) === 'number');
}

/**
 * @description: 是否为 Object
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function isObject(value: any): boolean {
    const type = typeof value;
    return value !== null && (type === 'object' || type === 'function');
}

/**
 * @description: 是否是 Array
 * @param {any} value
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function isArray(value: any): boolean {
    return Array.isArray(value);
}

/**
 * @description: 是否是 function
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function isFunction(value: any): boolean {
    return typeof value === 'function';
}

/**
 * @description: 是否是 string
 * @param {any} value
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function isString(value: any): boolean {
    const type = typeof value;
    return type === 'string' || (type === 'object' && getVarType(value) === 'string');
}

/**
 * @description: 是否是 boolean
 * @param {any} value
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function isBoolean(value: any): boolean {
    return value === true || value === false ||
        (typeof value === 'object' && getVarType(value) === 'boolean');
}

/**
 * @description: 是否是 Date
 * @param {any} value
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function isDate(value: any): boolean {
    const type = typeof value;
    return value != null && type === 'object' && getVarType(value) === 'date';
}

/**
 * @description: 是否是 Map
 * @param {any} value
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function isMap(value: any): boolean {
    const type = typeof value;
    return value != null && type === 'object' && getVarType(value) === 'map';
}

/**
 * @description: 是否是Map
 * @param {any} value
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function isSet(value: any): boolean {
    const type = typeof value;
    return value !== null && type === 'object' && getVarType(value) === 'set';
}

/**
 * @description: 检查value值是否为RegExp类型
 * @param {any} value
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function isRegexp(value: any): boolean {
    const type = typeof value;
    return value !== null && type === 'object' && getVarType(value) === 'regexp';
}

/**
 * @description: 返回value是否在 [min, max] 区间内
 * @param {number} min
 * @param {number} max
 * @param {number} value
 * @param {boolean} includeEdge true(默认值): [min, max]; false: (min, max)
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function inRange(min: number, max: number, value: number, includeEdge: boolean = true): boolean {
    return includeEdge ? value >= min && value <= max : value > min && value < max;
}

function hasCircularRef<T>(target: T): boolean {
    const visited = new WeakSet();
    function isCircular(obj) {
        if (typeof obj !== 'object' || obj === null) {
            // 基本类型或null直接返回false
            return false;
        }
        if (visited.has(obj)) {
            // 如果obj已经在visited中，说明存在循环引用
            return true;
        }
        // 将当前对象加入visited集合中
        visited.add(obj);
        // 遍历对象所有属性进行递归检测
        for (let key in obj) {
            if (isCircular(obj[key])) {
                return true;
            }
        }
        return false;
    }
    return isCircular(target);
}

/**
 * @description: 深拷贝
 * @param {T} target 目标
 * @return {T}
 * @author: yayalee
 * @since: v1.0.1
 */
function deepClone<T>(target: T, hash = new WeakMap()): T {
    //传入的对象不是对象 可能是基础数据类型直接返回
    if (target == null || typeof target !== 'object') return target;
    //循环检测是否有循环引用
    if (hash.has(target)) return hash.get(target);
    let result: T | Date | Set<T> | Map<any, any> | any = null;
    //#region Date
    if (target instanceof Date) {
        result = new Date();
        result.setTime(target.getTime());
        return result;
    }
    //#endregion
    //#region Array
    if (target instanceof Array) {
        result = [];
        for (let i = 0, length = target.length; i < length; i++) {
            result[i] = deepClone(target[i], hash);
        }
        return result;
    }
    //#endregion
    //#region Map
    if (target instanceof Map) {
        result = new Map();
        for (const [key, value] of target) {
            result.set(key, deepClone(value, hash));
        }
        return result;
    }
    //#endregion
    //#region Set
    if (target instanceof Set) {
        result = new Set();
        for (const value of target) {
            result.add(deepClone(value));
        }
        return result;
    }
    //#endregion
    //#region Object
    if (target instanceof Object) {
        result = {};
        hash.set(target, target);
        for (const key in target) {
            if (target.hasOwnProperty(key)) {
                result[key] = deepClone(target[key], hash);
            }
        }
        return result;
    }
    //#endregion
    //#region Function
    if (typeof target == 'function') return target;
    //#endregion
    console.warn(`deepClone 不支持的类型或循环引用：`, typeof target);
    return target;
}

function plainToInstance<T>(cls: new () => T, json: string | object): T {
    let cs = new cls();
    let jo = typeof json == 'string' ? parseJsonSafe(json) : json;
    Object.assign(cs, jo);
    return cs;
}

function parseJsonSafe<T>(json: string): T | null {
    try {
        return JSON.parse(json);
    } catch (error) {
        console.warn("json 解析错误: ", error);
        return null;
    }
}

function equal<T, U>(value: T, other: U): boolean;
function equal(value: any, other: any): boolean {
    if (isNumber(value) && isNumber(other))
        return approximatelyEqual(value, other);
    return value === other || (value !== value && other !== other);
}

/**
 * 判断两个数是否约等于
 * @param {number} a - 第一个数
 * @param {number} b - 第二个数
 * @param {number} epsilon - 容差值（可选）
 * @returns {boolean} - 若两个数约等于则返回 true，否则返回 false
 */
function approximatelyEqual(a: number, b: number, epsilon: number = Number.EPSILON): boolean {
    // 比较两个数的绝对差异与 epsilon 的大小
    return Math.abs(a - b) < epsilon;
}

/**
 * @description: 调用 iterator 遍历 collection(集合) 中的每个元素， 
 * iterator 调用3个参数： (value, index|key, collection)。
 * 如果迭代函数（iterator）显式的返回 false ，迭代会提前退出。 
 * 与其他"集合"方法一样，类似于数组，对象的 "length" 属性也会被遍历。想避免这种情况，可以用 forIn 
 * @param {any} collection  一个用来迭代的集合
 * @param {function} iterator 每次迭代调用的函数。如果 return false 那么迭代将会结束
 * @param {number} step 迭代的步数 默认为1 正常的for循环是1。如果为负数，则会从尾到开头遍历。如果为2就会步长就为2
 * @return {*}
 * @author: yayalee
 * @since: v1.0.1
 * ```
 * forEach([1,2,3,4,5],(v)=>console.log(v));
 * //=> 1, 2, 4, 5
 * forEach([1,2,3,4,5],(v)=>console.log(v),2);
 * //=> 1, 3, 5
 * forEach({a:1,b:2,c:3,d:4},(v,k)=>console.log(v,k));
 * //=> 1  a, 2  b, 3  c, 4 d
 * ```
 */
function forEach<T>(length: number, iterator: (index?: number) => boolean | void, step?: number): void;
function forEach<T>(array: Array<T>, iterator: (value?: T, index?: number, array?: Array<T>) => boolean | void, step?: number, startIndex?: number): void;
function forEach<T>(array: Array<T>, iterator: (value: T, index: number, array: Array<T>) => boolean | void, step?: number, startIndex?: number): void;
function forEach<T, U>(collection: any, iterator: (value: U, index: number, collection: T) => boolean | void, step: number = 1, startIndex: number = 0): void {
    if (!isFunction(iterator)) {
        console.warn("each iterator 类型错误", typeof iterator);
        return;
    }
    let iterateData = null;
    if (isArray(collection) || isString(collection) || isNumber(collection)) {
        iterateData = collection;
    } else if (isObject(collection)) {
        iterateData = Object.keys(collection);
    } else {
        console.warn("each 输入的参数无法遍历:", typeof collection);
        return;
    }
    equal(Math.floor(step), 0) && (step = 1);
    const length = isNumber(iterateData) ? iterateData : iterateData.length;
    const startIdx = startIndex > 0 ? startIndex : step < 0 ? length - 1 : 0;
    for (let i = startIdx; i >= 0 && i < length; i += step) {
        const key = iterateData[i];
        let eq = equal(iterateData, collection)
        const item = eq ? collection[i] : collection[key];
        let bool = iterator(item || i, eq ? i : key, collection);
        if (isBoolean(bool) && !bool) {
            break;  // 终止遍历
        }
    }
}

/**
 * @description: for in 对象遍历
 * @param {T} object
 * @param {function} iterator 迭代函数 return false 迭代结束
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function forIn<T, U>(object: T, iterator: (value?: U, key?: string, object?: T) => boolean | void): void {
    for (const key in object) {
        if (Object.prototype.hasOwnProperty.call(object, key)) {
            const value: U | any = object[key];
            let bool = iterator(value, key, object);
            if (isBoolean(bool) && !bool) {
                return;
            }
        }
    }
}

/**
 * @description: 遍历 collection（集合）元素，返回 predicate（断言函数）
 * 第一个返回真值的第一个元素。predicate（断言函数）调用3个参数： (value, index|key, collection)。
 * @param {Array} array 一个用来迭代的集合。
 * @param {array} predicate (Array|Function|Object|string): 每次迭代调用的函数。
 * @param {T} defaultValue 如果没找到返回默认值
 * @param {number} fromIndex 开始搜索的索引位置。
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 * ```
 * var users = [
 *   { 'user': 'barney',  'age': 36, 'active': true },
 *   { 'user': 'fred',    'age': 40, 'active': false },
 *   { 'user': 'pebbles', 'age': 1,  'active': true }
 * ];
 * find(users, function(o) { return o.age < 40; });
 * // => object for 'barney'
 * find(users, { 'age': 1, 'active': true });
 * // => object for 'pebbles'
 * find(users, ['active', false]);
 * // => object for 'fred'
 * find(users, 'active');
 * // => object for 'barney'
 * ```
 */
function find<T, U, V>(array: Array<T>, predicate: [U, V], defaultValue?: T | null, fromIndex?: number): T;
function find<T>(array: Array<T>, predicate: (item: T) => any, defaultValue?: T | null, fromIndex?: number): T;
function find<T>(array: Array<T>, predicate: Object, defaultValue?: T | null, fromIndex?: number): T;
function find<T>(array: Array<T>, predicate: number, defaultValue?: T | null, fromIndex?: number): { index: number, value: number };
function find<T>(array: Array<T>, predicate: string, defaultValue?: T | null, fromIndex?: number): T;
function find<T>(array: Array<T>, predicate: any, defaultValue?: T | null, fromIndex: number = 0): T {
    if (!isArray(array)) {
        console.warn("find array 类型错误:", typeof array);
        return defaultValue!;
    }
    if (isNumber(predicate)) {
        let index = array.indexOf(predicate);
        let value = array[index];
        return { index, value } as any;
    }
    let predicateFunc: (item: T) => any = findIterated(predicate);
    for (let i = fromIndex; i < array.length; i++) {
        const item = array[i];
        if (predicateFunc(item)) {
            return item;
        }
    }
    return defaultValue!;
}

/**
 * @description: find 迭代器/反复声明
 * @param {Object} arg
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function findIterated<T>(arg: Object): (data: T) => any
function findIterated<T>(arg: string): (data: T) => any;
function findIterated<T>(arg: Array<T>): (data: T) => any;
function findIterated<T>(arg: any): (data: T) => any {
    let func: (item: T) => any;
    if (isFunction(arg)) {
        func = arg;
    } else if (isString(arg)) {
        func = (obj: any) => obj[arg];
    } else if (isArray(arg)) {
        func = (obj: any) => obj[arg[0]] === arg[1];
    } else {
        const keys = Object.keys(arg);
        func = (obj: any) => {
            for (let i = 0; i < keys.length; i++) {
                if (obj[keys[i]] !== arg[keys[i]]) {
                    return false;
                }
            }
            return true;
        }
    }
    return func;
}

/**
 * @description: 数组 数据分组
 * @param {Array} array 数组
 * @param {string} generateKey 根据传过来的Key进行分组
 * @return {*} 返回一个新的对象 数组 {a:[]}
 * @author: yayalee
 * @since: v1.0.0
 * ```
 * let array = [
 *      {id:1,name:"张三"},
 *      {id:1,name:'李四'},
 *      {id:2,name:'小明'},
 *      {id:2,name:'小李'}
 * ]
 * _js.groupBy(array,"id");
 * //=> {1:[{id:1,name:"张三"},{{id:1,name:'李四'}}],2:[{id:2,name:'小明'},{id:2,name:'小李'}]};
 * _js.groupBy(array,(item)=>item[name]);
 * //=> {"张三":[{id:1,name:"张三"}],"李四":[{id:1,name:'李四'}],小明:[{id:2,name:'小明'}],"小李":[{id:2,name:'小李'}]};
 * ```
 */
function groupBy<T, U>(array: Array<T>, generateKey: string): Array<U>[];
function groupBy<T, U>(array: Array<T>, generateKey: ((item: T) => string)): Array<U>[];
function groupBy<T, U>(array: Array<T>, generateKey: any): Array<U> {
    if (typeof generateKey === 'string') {
        const propName = generateKey;
        generateKey = (item: T | any) => item[propName];
    }
    const result = Object.create(null);
    for (const item of array) {
        const key = generateKey(item);
        if (!result[key]) {
            result[key] = [];
        }
        result[key].push(item);
    }
    return result;
}

/**
  * 
  * @param executor 
  * @param onFullfiled 
  * @param onRejected 
  * @returns 
  */
function Coop<T>(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: T | PromiseLike<T>) => void) => void, onFullfiled?: ((value: T) => T | PromiseLike<T>) | undefined | null, onRejected?: ((reason: T) => T | PromiseLike<T>) | undefined | null): Promise<T> {
    let promise = new Promise(executor);
    if (onFullfiled) {
        if (onRejected) {
            let isHandled = false;
            promise.then((result) => {
                if (!isHandled) {
                    isHandled = true;
                    onFullfiled(result);
                }
            }).catch((error) => {
                console.warn("coop error", error);
                if (!isHandled) {
                    isHandled = true;
                    onRejected(error);
                }
            });
        } else {
            promise.then(onFullfiled).catch((error) => {
                console.warn("coop rejected", error);
            });
        }
    } else if (onRejected) {
        promise.catch((error) => {
            console.warn("coop error", error);
            onRejected(error);
        });
    } else {
        promise.catch((error) => {
            console.warn("coop rejected", error);
        });
    }
    return promise;
}

/**
 * @description: 等待-setTimeOut
 * @param {number} msec 毫秒
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 */
function wait(msec: number): Promise<void> {
    return Coop((resolve) => {
        setTimeout(() => {
            resolve();
        }, msec);
    })
}
/**
 * sleep == wait;
 */
function sleep(msec: number): Promise<void> {
    return wait(msec);
}

/**
 * @description: 去除抖动函数
 * @param {Function} fun
 * @param {number} duration
 * @return {*}
 * @author: yayalee
 * @since: v1.0.0
 * ```
 *  * let handler = ()=>{ console.log("test") }
 * let debounceHandler = _js.debounce(handler,1000);
 * for(i=0,i<4,i++){
 *  debounceHandler();
 * }
 * //=> "test" //1次
 * 
 * 类里面使用
 * class A {
 *  debounceHandler = _js.debounce((str)=>{console.log(str)});
 *  test(){
 *      this.debounceHandler("test");
 *  }
 * }
 * let a = new A();
 * for(i=0,i<4,i++){
 *  a.test();
 * }
 * //=> "test" //1次
 * ```
 */
function debounce(fun: Function, duration: number = 1): (...args: any) => number {
    let timeOutId: number | any;//记录上一次的计时id
    return function (...args: any): number {
        clearTimeout(timeOutId);
        timeOutId = setTimeout(() => {
            //@ts-ignore
            fun.apply(this, args);
        }, duration);
        return timeOutId;
    }
}

export {
    getVarType, isNull, isEmpty,
    isNumber, isObject, isArray, isFunction, isString, isBoolean,
    isDate, isMap, isSet, isRegexp,



    inRange, hasCircularRef, deepClone, plainToInstance, equal, approximatelyEqual,

    forEach, forIn, find,

    groupBy, Coop, wait, sleep, debounce,
}
